﻿using System;
using System.Diagnostics.Contracts;
using EPiServer.Core;

namespace OpenWaves.EPiServer.CustomProperties
{
    [ContractClass(typeof(StringPropertyContract<>))]
    public abstract class StringProperty<T> : PropertyData, IPropertyData<T>
    {
        private T value;

        protected StringProperty()
        {
            Contract.Requires(typeof(T) != typeof(string), "Implementation details of this class assume T is not string (see LoadData method).");
        }

        public override PropertyDataType Type
        {
            get { return PropertyDataType.String; }
        }

        public override Type PropertyValueType
        {
            get { return Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); }
        }

        public T GetValue()
        {
            return this.value;
        }

        public void SetValue(T value)
        {
            this.SetPropertyValue(value, delegate
            {
                if (Equals(value, this.DefaultValue))
                {
                    this.Clear();
                }
                else
                {
                    this.value = value;
                    this.Modified();
                }
            });
        }

        public override object Value
        {
            get { return this.GetValue(); }
            set { this.SetValue((T)value); }
        }

        protected override void SetDefaultValue()
        {
            this.ThrowIfReadOnly();
            this.value = this.DefaultValue;
        }

        public override void LoadData(object value)
        {
            // HACK: EPi sometimes (from CreateLanguageBranch) will pass deserialized value
            if (value is T)
                this.SetValue((T) value);
            else
                this.SetValue(this.DeserializeValueWithCheck((string)value));
        }

        public override object SaveData(PropertyDataCollection properties)
        {
            return this.SerializeValueWithCheck(this.GetValue());
        }

        private T DeserializeValueWithCheck(string value)
        {
            try
            {
                return this.DeserializeValue(value);
            }
            catch (Exception e)
            {
                throw new PropertySerializationException(
                    "Failed to deserialize property value.", this.Name, this.GetParentLink(), value, e);
            }
        }

        private string SerializeValueWithCheck(T value)
        {
            try
            {
                return this.SerializeValue(value);
            }
            catch (Exception e)
            {
                throw new PropertySerializationException(
                    "Failed to serialize property value.", this.Name, this.GetParentLink(), value, e);
            }
        }

        public override PropertyData ParseToObject(string value)
        {
            return this.CreateInstance(this.DeserializeValueWithCheck(value));
        }

        public override void ParseToSelf(string value)
        {
            this.SetValue(this.DeserializeValueWithCheck(value));
        }

        protected abstract T DefaultValue { get; }
        protected abstract StringProperty<T> CreateInstance(T value);
        protected abstract string SerializeValue(T value);
        protected abstract T DeserializeValue(string serializedValue);        
    }

    [ContractClassFor(typeof(StringProperty<>))]
    abstract class StringPropertyContract<T> : StringProperty<T>
    {
        protected override StringProperty<T> CreateInstance(T value)
        {
            Contract.Ensures(Contract.Result<StringProperty<T>>() != null);
            throw new NotImplementedException();
        }
    }
}